using LoggingMagic.AssemlyOperations;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace LoggingMagic.Injectors.Helpers
{
    internal class MethodParametersToArray
    {
        private readonly TypeReferencesProvider _referencesProvider;

        public MethodParametersToArray(TypeReferencesProvider referencesProvider)
        {
            _referencesProvider = referencesProvider;
        }

        /// <summary>
        /// returns a reference to a local variable of object[] containing all the parameters of the method
        /// </summary>
        /// <param name="method"></param>
        /// <param name="ops"></param>
        /// <returns></returns>
        public VariableDefinition Convert(MethodDefinition method, Instructions ops)
        {
            method.Body.InitLocals = true;
            var parameters = new VariableDefinition(_referencesProvider.ArrayOfObjects);
            method.Body.GetILProcessor().Body.Variables.Add(parameters);


            #region parameters = new object[Params.Count];

            //new object[1]
            ops.Add(Instruction.Create(OpCodes.Ldc_I4, method.Parameters.Count));
            ops.Add(Instruction.Create(OpCodes.Newarr, _referencesProvider.Object));

            //assign to `parameters` variable
            ops.Stloc(parameters);

            #endregion

            #region parameters[i] = paramValue

            var i = 0;
            foreach (var param in method.Parameters)
            {
                //loading reference to an array
                ops.Ldloc(parameters);
                //loading index
                ops.Add(Instruction.Create(OpCodes.Ldc_I4, i));
                //loading value
                ops.Ldarg(param);

                if (param.ParameterType.IsValueType)
                {
                    //perform boxing for value types
                    ops.Add(Instruction.Create(OpCodes.Box, param.ParameterType));
                }

                //adding value to an array
                ops.Add(Instruction.Create(OpCodes.Stelem_Ref));

                i++;
            }

            #endregion

            return parameters;
        }
    }
}